from flask import Flask, render_template, jsonify, request, redirect, url_for, flash, session
from pymongo import MongoClient
from data.locations import LOCATIONS
import os
from dotenv import load_dotenv
import logging
import threading
from datetime import timedelta
from functools import wraps
from flask_cors import CORS
from contextlib import contextmanager
import time
from datetime import datetime
from bson import ObjectId
import requests
import hashlib
from werkzeug.utils import secure_filename
from urllib.parse import urlparse

# 加载环境变量
load_dotenv()

# 配置日志
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)

app = Flask(__name__)
CORS(app, resources={
    r"/api/*": {
        "origins": ["chrome-extension://*", "http://localhost:5000"],
        "methods": ["GET", "POST", "PUT", "DELETE"],
        "allow_headers": ["Content-Type"]
    }
})
app.config['SECRET_KEY'] = 'your-secret-key-here'  # 简化密钥
app.config['SESSION_COOKIE_HTTPONLY'] = True

# 简化登录验证
ADMIN_USERNAME = 'admin'
ADMIN_PASSWORD = 'admin123'

# 登录检查装饰器
def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not session.get('logged_in'):
            flash('请先登录')
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

# 从环境变量获取MongoDB凭据
MONGO_USER = os.getenv('MONGO_USER')
MONGO_PASSWORD = os.getenv('MONGO_PASSWORD')
MONGO_HOST = os.getenv('MONGO_HOST', 'localhost')
MONGO_PORT = int(os.getenv('MONGO_PORT', '2641'))  # 使用.env中配置的端口

# MongoDB连接类
class MongoDBConnection:
    _instance = None
    _lock = threading.Lock()
    
    def __init__(self):
        self.client = None
        self.db = None
        self.posts = None
    
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance
    
    def connect(self):
        if self.client is None:
            try:
                # 构建MongoDB连接URI
                if MONGO_USER and MONGO_PASSWORD:
                    MONGODB_URI = f"mongodb://{MONGO_USER}:{MONGO_PASSWORD}@{MONGO_HOST}:{MONGO_PORT}/redbook?authSource=admin"
                else:
                    MONGODB_URI = f"mongodb://{MONGO_HOST}:{MONGO_PORT}/redbook"
                
                self.client = MongoClient(MONGODB_URI)
                self.db = self.client.redbook
                self.posts = self.db.posts
                
                # 测试连接
                self.client.admin.command('ping')
                logger.info("Successfully connected to MongoDB")
                return self.db
            except Exception as e:
                logger.error(f"MongoDB connection error: {str(e)}")
                if self.client:
                    self.client.close()
                self.client = None
                self.db = None
                self.posts = None
                raise e
        return self.db

    def get_connection(self):
        if self.client is None:
            self.connect()
        return self.db
    
    def close(self):
        if self.client:
            self.client.close()
            self.client = None
            self.db = None
            self.posts = None

# 获取数据库连接的函数
def get_db():
    try:
        db_conn = MongoDBConnection()
        return db_conn.connect()
    except Exception as e:
        logger.error(f"Failed to get database connection: {str(e)}")
        raise e

# 登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
    # 如果已经登录，直接跳转到管理页面
    if session.get('logged_in'):
        return redirect(url_for('admin'))
        
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        
        if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
            session['logged_in'] = True
            session.permanent = True
            return redirect(url_for('admin'))
        else:
            flash('用户名或密码错误')
            return render_template('login.html')
    
    return render_template('login.html')

# 登出路由
@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('login'))

# 首页路由
@app.route('/')
def index():
    return render_template('index.html', locations=LOCATIONS)
        
# 后台管理路由
@app.route('/admin')
@login_required
def admin():
    try:
        logger.info("Accessing admin page...")
        
        # 获取数据库连接
        db = get_db()
        posts = db.posts
        
        try:
            # 获取所有文章，按创建时间倒序排列
            all_posts = list(posts.find().sort('created_at', -1))
            logger.info(f"Successfully retrieved {len(all_posts)} posts")
            
            # 处理日期格式
            processed_posts = []
            for post in all_posts:
                try:
                    processed_post = post.copy()  # 创建副本以避免修改原始数据
                    processed_post['_id'] = str(post['_id'])
                    
                    # 检查并处理created_at
                    if 'created_at' in processed_post:
                        created_at = processed_post['created_at']
                        logger.debug(f"Processing created_at: {created_at} (type: {type(created_at)})")
                        
                        if isinstance(created_at, datetime):
                            processed_post['created_at'] = created_at.strftime('%Y-%m-%d %H:%M:%S')
                        elif isinstance(created_at, str):
                            # 如果是字符串，检查是否需要解析
                            try:
                                # 尝试解析已有的日期字符串
                                dt = datetime.strptime(created_at, '%Y-%m-%d %H:%M:%S')
                                processed_post['created_at'] = created_at  # 已经是正确格式，直接使用
                            except ValueError:
                                # 如果解析失败，使用当前时间
                                processed_post['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                        else:
                            # 其他类型，使用当前时间
                            processed_post['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                    else:
                        # 如果没有created_at字段，添加当前时间
                        processed_post['created_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                    
                    processed_posts.append(processed_post)
                except Exception as e:
                    logger.error(f"Error processing post {post.get('_id', 'unknown')}: {str(e)}")
                    # 继续处理下一篇文章
                    continue
            
            logger.info(f"Successfully processed {len(processed_posts)} posts")
            return render_template('admin.html', posts=processed_posts)
            
        except Exception as e:
            logger.error(f"Failed to retrieve posts: {str(e)}")
            flash('获取文章列表失败')
            return render_template('admin.html', posts=[])
            
    except Exception as e:
        logger.error(f"Admin page error: {str(e)}")
        flash('系统错误，请稍后重试')
        return render_template('admin.html', posts=[])

# 图片处理函数
def process_image(image_url):
    """
    下载并保存图片，返回本地路径
    如果失败则返回原始URL
    """
    if not image_url:
        return ''
        
    try:
        # 下载图片
        response = requests.get(image_url, timeout=10)
        if response.status_code == 200:
            # 获取文件扩展名
            content_type = response.headers.get('content-type', '')
            ext = ''
            if 'image/jpeg' in content_type:
                ext = '.jpg'
            elif 'image/png' in content_type:
                ext = '.png'
            elif 'image/gif' in content_type:
                ext = '.gif'
            elif 'image/webp' in content_type:
                ext = '.webp'
            else:
                # 从URL中获取扩展名
                parsed_url = urlparse(image_url)
                path = parsed_url.path
                ext = os.path.splitext(path)[1].lower()
                if not ext or ext[1:] not in ALLOWED_EXTENSIONS:
                    ext = '.jpg'  # 默认使用jpg
            
            # 使用URL生成唯一文件名
            file_hash = hashlib.md5(image_url.encode()).hexdigest()
            filename = secure_filename(f"{file_hash}{ext}")
            filepath = os.path.join(UPLOAD_FOLDER, filename)
            
            # 如果文件已存在，直接返回路径
            if os.path.exists(filepath):
                return f'/static/images/{filename}'
            
            # 保存图片
            with open(filepath, 'wb') as f:
                f.write(response.content)
            
            # 返回本地路径
            local_path = f'/static/images/{filename}'
            logger.info(f"Image saved successfully: {local_path}")
            return local_path
            
    except Exception as e:
        logger.error(f"下载图片失败: {str(e)}")
    
    # 如果出现任何错误，返回原始URL
    return image_url

@app.route('/admin/posts/new', methods=['GET', 'POST'])
@login_required
def new_post():
    if request.method == 'POST':
        try:
            # 获取数据库连接
            db = get_db()
            posts = db.posts
            
            title = request.form.get('title')
            content = request.form.get('content')
            url = request.form.get('url')
            hook = request.form.get('hook', '')
            keywords = request.form.get('keywords', '')
            cover = request.form.get('cover', '')
            is_hot = request.form.get('is_hot') == 'on'
            has_volume = request.form.get('has_volume') == 'on'
            
            # 处理封面图片
            local_cover_path = process_image(cover)
            
            # 创建文章
            post = {
                'title': title,
                'content': content,
                'url': url,
                'hook': hook,
                'keywords': keywords,
                'cover': local_cover_path,
                'is_hot': is_hot,
                'has_volume': has_volume,
                'created_at': datetime.now()
            }
            
            result = posts.insert_one(post)
            
            if result.inserted_id:
                flash('文章创建成功！')
                return redirect(url_for('admin'))
            else:
                flash('文章创建失败，请重试')
                return render_template('post_form.html')
                
        except Exception as e:
            logger.error(f"Create post error: {str(e)}")
            flash('创建文章失败，请重试')
            return render_template('post_form.html')
            
    return render_template('post_form.html')

@app.route('/admin/posts/<post_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_post(post_id):
    try:
        # 获取数据库连接
        db = get_db()
        posts = db.posts
        
        if request.method == 'POST':
            # 获取表单数据
            title = request.form.get('title')
            content = request.form.get('content')
            url = request.form.get('url')
            hook = request.form.get('hook', '')
            keywords = request.form.get('keywords', '')
            cover = request.form.get('cover', '')
            is_hot = request.form.get('is_hot') == 'on'
            has_volume = request.form.get('has_volume') == 'on'
            
            # 处理封面图片
            local_cover_path = process_image(cover)
            
            # 更新文章
            update_data = {
                'title': title,
                'content': content,
                'url': url,
                'hook': hook,
                'keywords': keywords,
                'cover': local_cover_path,
                'is_hot': is_hot,
                'has_volume': has_volume,
                'updated_at': datetime.now()
            }
            
            result = posts.update_one(
                {'_id': ObjectId(post_id)},
                {'$set': update_data}
            )
            
            if result.modified_count > 0:
                flash('文章更新成功！')
                return redirect(url_for('admin'))
            else:
                flash('文章更新失败，请重试')
        
        # GET请求，显示编辑表单
        post = posts.find_one({'_id': ObjectId(post_id)})
        if post:
            post['_id'] = str(post['_id'])  # 转换ObjectId为字符串
            return render_template('post_form.html', post=post)
        else:
            flash('文章不存在')
            return redirect(url_for('admin'))
            
    except Exception as e:
        logger.error(f"Edit post error: {str(e)}")
        flash('编辑文章失败，请重试')
        return redirect(url_for('admin'))

@app.route('/admin/posts/<post_id>/delete', methods=['POST'])
@login_required
def delete_post(post_id):
    try:
        # 获取 posts 集合
        db = get_db()
        posts = db.posts
        
        result = posts.delete_one({'_id': ObjectId(post_id)})
        if result.deleted_count:
            flash('文章删除成功！', 'success')
        else:
            flash('文章删除失败！', 'error')
    except Exception as e:
        logger.error(f"Delete post error: {str(e)}")
        flash('删除文章失败', 'error')
    return redirect(url_for('admin'))

@app.route('/api/toggle-volume', methods=['POST'])
def toggle_volume():
    try:
        data = request.json
        post_id = data.get('post_id')
        
        # 获取当前文章
        db = get_db()
        posts = db.posts
        post = posts.find_one({'_id': ObjectId(post_id)})
        if not post:
            return jsonify({'success': False, 'message': '文章不存在'})
        
        # 切换状态
        current_status = post.get('has_volume', 'no')
        new_status = 'yes' if current_status != 'yes' else 'no'
        
        # 更新文
        result = posts.update_one(
            {'_id': ObjectId(post_id)},
            {'$set': {'has_volume': new_status}}
        )
        
        if result.modified_count > 0:
            return jsonify({'success': True, 'new_status': new_status})
        else:
            return jsonify({'success': False, 'message': '更新失败'})
            
    except Exception as e:
        app.logger.error(f"Toggle volume error: {str(e)}")
        return jsonify({'success': False, 'message': str(e)})

# 配置上传文件夹
UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'images')
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}

# 确保上传目录存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

# 文章创建API
@app.route('/api/articles/create', methods=['POST'])
def create_article():
    try:
        data = request.get_json()
        
        # 确保必要字段存在
        required_fields = ['title', 'content', 'url']
        for field in required_fields:
            if field not in data:
                return jsonify({'error': f'缺少必要字段：{field}'}), 400
        
        # 处理封面图片
        cover_url = data.get('cover', '')
        local_cover_path = process_image(cover_url)
        
        # 准备文章数据
        article = {
            'title': data['title'],
            'content': data['content'],
            'url': data['url'],
            'hook': data.get('hook', ''),
            'is_hot': data.get('is_hot', False),
            'keywords': data.get('keywords', ''),
            'cover': local_cover_path,
            'interaction': '{}点赞 {}评论 {}收藏'.format(
                data.get('like', '0'),
                data.get('comment', '0'),
                data.get('collect', '0')
            ),
            'date': data.get('date', ''),
            'created_at': datetime.now(),
            'has_volume': data.get('has_volume', False)
        }
        
        # 获取数据库连接并插入文章
        db = get_db()
        result = db.posts.insert_one(article)
        
        return jsonify({
            'message': '文章创建成功！',
            'id': str(result.inserted_id)
        }), 201
        
    except Exception as e:
        logger.error(f"创建文章失败: {str(e)}")
        return jsonify({'error': '创建文章失败'}), 500

# 搜索路由
@app.route('/api/search')
def search_posts():
    try:
        location = request.args.get('location', '')
        keyword = request.args.get('keyword', '')
        
        # 获取数据库连接
        db = get_db()
        if db is None:
            logger.error("Database connection not available")
            return jsonify({"error": "数据库连接失败，请重试"}), 500
            
        posts = db.posts
        if posts is None:
            logger.error("Posts collection not available")
            return jsonify({"error": "数据库集合不可用，请重试"}), 500
        
        # 构建查询条件
        query = {}
        if location:
            # 构建地区查询条件
            location_query = {
                '$or': [
                    {'location': location},  # 精确匹配location字段
                    {'content': {'$regex': location.replace('市', '').replace('区', ''), '$options': 'i'}}  # 内容中包含地区名（去掉"市"和"区"后缀）
                ]
            }
            if keyword:
                # 如果有关键词，用$and组合
                query = {
                    '$and': [
                        location_query,
                        {
                            '$or': [
                                {'title': {'$regex': keyword, '$options': 'i'}},
                                {'content': {'$regex': keyword, '$options': 'i'}}
                            ]
                        }
                    ]
                }
            else:
                query = location_query
        elif keyword:
            # 只有关键词时
            query = {
                '$or': [
                    {'title': {'$regex': keyword, '$options': 'i'}},
                    {'content': {'$regex': keyword, '$options': 'i'}}
                ]
            }
        
        # 执行查询
        results = list(posts.find(
            query,
            {
                '_id': 1,
                'title': 1,
                'content': 1,
                'url': 1,
                'is_hot': 1,
                'hook': 1,
                'cover': 1,
                'keywords': 1,
                'interaction': 1,
                'date': 1,
                'created_at': 1,
                'has_volume': 1,
                'location': 1
            }
        ).sort('created_at', -1))
        
        # 处理结果
        for result in results:
            result['_id'] = str(result['_id'])
            if 'created_at' in result and result['created_at']:
                result['created_at'] = result['created_at'].strftime('%Y-%m-%d %H:%M:%S')
            
            # 处理封面图片路径
            if 'cover' in result and result['cover']:
                if result['cover'].startswith('http'):
                    # 如果是网络图片，检查是否已经有本地副本
                    local_path = os.path.join('static', 'images', hashlib.md5(result['cover'].encode()).hexdigest() + '.jpg')
                    if os.path.exists(local_path):
                        result['cover'] = '/' + local_path
                    else:
                        # 如果没有本地副本，使用默认图片
                        result['cover'] = '/static/images/default.jpg'
                elif not result['cover'].startswith('/'):
                    # 如果是相对路径，添加前缀
                    result['cover'] = '/' + result['cover']
        
        return jsonify(results)
        
    except Exception as e:
        logger.error(f"Search error: {str(e)}")
        return jsonify({"error": "搜索功能出错了"}), 500

# 在应用关闭时清理连接
@app.teardown_appcontext
def cleanup(error):
    db_conn = MongoDBConnection()
    db_conn.close()

if __name__ == '__main__':
    app.run(debug=True)
